#include "kernel/initializer.hpp"
#include "yocto/code/hsort.hpp"

ChemIni:: ChemIni( const Library &lib, const Real V ) :
value( V ), terms_(), library(lib)
{
	
}

ChemIni:: ~ChemIni() throw()
{
}

ChemIni:: ChemIni( const ChemIni &ci ) :
value( ci.value ),
terms_( ci.terms_ ),
library( ci.library )
{
}

static inline 
int compare_terms( const ChemIni::Term &lhs, const ChemIni::Term &rhs ) throw()
{
	return string::compare(lhs.name,rhs.name);
}

void ChemIni:: append( const Term &t )
{
	if( !library.has( t.name ) )
		throw exception("*** Unknown Term name '%s'", t.name.c_str() );
	
	for( size_t i=terms_.size(); i>0; --i )
	{
		if( terms_[i].name == t.name )
			throw exception("*** Multiple Term '%s'", t.name.c_str() );
	}
	
	terms_.push_back( t );
	hsort( terms_, compare_terms );
	
}

bool ChemIni:: is_same_as( const ChemIni &other ) const throw()
{
	if( terms_.size() != other.terms_.size() ) return false;
	for( size_t i=terms_.size();i>0;--i)
	{
		if( terms_[i].name != other.terms_[i].name )
			return false;
	}
	return true;
}

Initializer:: ~Initializer() throw()
{
}

Initializer:: Initializer( const Library &lib ) : 
Nc(0),
M(0),
N(0),
I(),
C(),
Q(),
X0(),
lss(),
library(lib),
C0(1e-5)
{
}

void Initializer:: append( const ChemIni &ci )
{
	for( size_t i=I.size(); i>0; --i )
	{
		if( I[i].is_same_as(ci) )
			throw exception("*** Redundant initial specification!");
	}
	I.push_back(ci);
}

void Initializer:: load( lua_State *L, const string &ini_name )
{
	assert(L);
	std::cerr << "---------------- Loading Initializer '" << ini_name << "' ----------------" << std::endl;

	lua_settop(L, 0);
	lua_getglobal(L,ini_name.c_str());
	if( !lua_istable(L,-1) )
	{
		throw exception("*** Invalid type for initializer '%s'", ini_name.c_str() );
	}
	
	const size_t n = lua_objlen(L, -1);
	std::cerr << "-- Loading " << n << " condition(s)" << std::endl;
	for( unsigned i=1; i <=n; ++i )
	{
		//======================================================================
		// access the condition
		//======================================================================
		lua_rawgeti(L, -1, i);
		if( !lua_istable(L,-1) )
			throw exception("*** Initializer<%s>: Invalid type for condition #%u", ini_name.c_str(), i);
		
		//----------------------------------------------------------------------
		// parse the condition
		//----------------------------------------------------------------------
		const size_t nt = lua_objlen(L, -1);
		
		//-- get the constant
		lua_rawgeti(L, -1, 1);
		if( lua_isnil(L,-1) )
		{
			std::cerr << "-- [ Interrupted Loading] " << std::endl;
			break; //-- stop parsing
		}
		if( nt < 2 )
			throw exception("*** Initializer<%s>: Missing Terms for condition #%u", ini_name.c_str(), i);
		
		if( !lua_isnumber(L,-1) )
		{
			throw exception("*** Initializer<%s>: invalid type for condition #%u constant", ini_name.c_str(), i);
		}
		const Real value = lua_tonumber(L, -1);
		std::cerr << "-- => " << value << " = ";	
		lua_pop(L,1);
		
		ChemIni ci(library,value);
		//-- parse the weight/name
		for( unsigned j=2; j <= nt; ++j )
		{
			//-- extract the term
			lua_rawgeti(L, -1, j);
			if( !lua_istable(L,-1) )
				throw exception("*** Initializer<%s>: invalid type for condition #%u, term %u", ini_name.c_str(), i, j-1);
			
			//-- get the weight
			lua_rawgeti(L, -1, 1);
			if( !lua_isnumber(L, -1) )
			{
				throw exception("*** Initializer<%s>: invalid type for WEIGHT of condition #%u, term %u", ini_name.c_str(), i, j-1);
			}
			const Real t_weight = lua_tonumber(L, -1);
			lua_pop(L,1);
			std::cerr << "(" << t_weight << ")";
			
			//-- get the name
			lua_rawgeti(L, -1, 2);
			if( !lua_isstring(L, -1) )
			{
				throw exception("*** Initializer<%s>: invalid type for NAME of condition #%u, term %u", ini_name.c_str(), i, j-1);
			}
			size_t       n_len = 0;
			const char  *n_txt = lua_tolstring(L, -1, &n_len);
			const string t_name(n_txt,n_len);
			lua_pop(L,1);
			std::cerr << "*[" << t_name << "]";
			if( j < nt ) std::cerr << " + ";
			
			//-- remove the term from stack
			lua_pop(L,1);
			
			//-- create real term
			const ChemIni::Term term(t_weight,t_name);
			ci.append( term );
		}
		
		
		//----------------------------------------------------------------------
		// done parsing condition
		//----------------------------------------------------------------------
		std::cerr << std::endl;
		this->append( ci );
		//======================================================================
		// done...
		//======================================================================
		lua_pop(L,1);
	}
	
	std::cerr << "---------------- Loaded  Initializer '" << ini_name << "' ----------------" << std::endl << std::endl;

}
